home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HPAVC
/
HPAVC CD-ROM.iso
/
ASMVEG2.ZIP
/
ASMVEG2.TXT
next >
Wrap
Text File
|
1996-12-23
|
14KB
|
340 lines
Assembly Language for Veggies (And C programmers) Part 2.
So, you've wound your way through part one, lashed out and bought the book and
now you're about to leap into things in a big way, right?
OK... well here is where we make a bit of a start on things! We'll be looking
first off at a simple routine that displays a number on the screen. sound
simple? You Wait!
One of the more vital routines one uses from time to time [read all the time!]
is a simple write number to screen routine. consider what isrequired here.. to
take a number held wither in a register or memory and display it on the
screen. simple? you think about the work required to do it and you'll
start to understand just how hard it is to do...
Firstly, consider the biggest number you wish to write... if it's 8 bits or
less (0 -255) then you can use one routine, whilst a 16 bit (0-65535) routine
would be more versatile, BUT if you need to do really big numbers (like free
space on a hard disk for example) thena 32 bit routine would be called for...
Perhaps the most common of all is the 16 bit routine....
It is coded thus:
WRITE_ASCII: PUSH AX
PUSH CX
PUSH DX
PUSH SI
MOV AX,DX
MOV SI,10
XOR CX,CX
NON_ZERO: XOR DX,DX
DIV SI
PUSH DX
INC CX
OR AX,AX
JNE NON_ZERO
WRITE_DIGIT_LOOP: POP DX
CALL WRITE_HEX_DIGIT
LOOP WRITE_DIGIT_LOOP
END_DECIMAL: POP SI
POP DX
POP CX
POP AX
RET
WRITE_HEX_DIGIT: PUSH DX
CMP DL,10
JAE HEX_LETTER
ADD DL,'0'
JMP SHORT WRITE_DIGIT
HEX_LETTER: ADD DL,'A'-10
WRITE_DIGIT: CALL PRT1
POP DX
RET
PRT1: PUSH AX
MOV AH,02
INT 021
POP AX
RET
That is one BIG routine..... the Pascal version of which whould be :
program write_num
Var
number : word;
begin
number := 5285; {Say}
write(number);
end.
We actually have three separate routines here, one called WRITE_ASCII which is
the one we call, and two more subroutines which write_ascii calls itself. Can
you name them? They are referanced by CALL instructions... They are
write_hex_digit and prt1. write_hex_digit calls prt1 so we basicly have a 3
level nested loop arrangement.
Looking complex yet?
Time to step into the code in more detail... When the routine is first called,
the number to be displayed should be present in the DX register.
The first job our routine has to do is to convert the number in the register
to decimal, and then into actuall ASCII digits suitable for writing to the
screen. Here is where we get tricky... We use a loop and some simple maths to
derive the decimal equivilant, and the number of loops is equal to the number
of digits in the final ascii number.
The first instruction you see is the PUSH command. What this does is put the
contents of the named register upon the stack. In effect, this saves the
contents in a more or less indestructable area for later recall.. One can
safely fiddle with the contents of the register, safe in the knowledge that
it's original contents can be recalled with the use of the POP instruction.
note that PUSH's and POP's must be done in order... if one does a push AX,
push bx, then later does a pop ax, pop bx, the contents of ax and bx will be
exchanged... it's the same as any other stack operation, so keep things in
order!!
We save the AX, CX, DX snd SI registers.... WHY?? Think about this for a
second or two.. We save these registers because we modify them in our
routine! now, why is this important?? Because the main program may too be
using them!
If you find this hard to understand, follow this analogy... you have a Car
radio in your car, tuned to your favourite station (MMM-FM 105!) .. you take
it to the garage for a service. When you get it back you'd expect it to still
be on MMM wouldn't you? Of course!! Now the average garage jock likes FOX
better, so he sticks it on fox. If he returned the car to you still on fox,
you'd be upset and annoyed.... if he was kind enough to return it to MMM
before giving it back, you'd be none the wizer and go about your way happy as
larry as the saying goes.
To save our main program from bieng upset (read crashed) by alteration of it's
registers, our routine saves them on entry and (as you'll soon see) recalls
them just before exiting. Clear?
OK... now the register-to-decimal routine... involves the loop from
NON_ZERO: to the JNE instruction BEFORE the WRITE_DIGIT_LOOP: label. before
entry, some registers are setup... AX is loaded with our initial value. (DX
stil holds this as well) SI is loaded with 10 decimal and cx is made equal to
zero (boolean logic dictates that an xor of any number with itself results in
0) by the XOR cx,cx instruction.
Firstly DX is zeroed.
AX is ten divided by SI (10) ... the result is a decimal digit in AX (The
quotient) and a remainder in AX.
This bit of math goes like this:
Let's say you called the program with 36h in DX. 36h goes into AX, and 10d
into CX. 36 hex is the same as 54 decimal. 36h(ex) divided by 10d(ecimal)
equals 5 in AX and 4 in DX.
DX now holds our lest signivigant digit (the 1's unit of you like) - the
number 4. DX is saved on the stack for use in a second.
CX is incremented from 0 to one. this counts the fact that 1 digit has been
saved so far.
the or AX,AX basicly checks to see if ax is zero or not. the JNE stands for
Jump not equal.. this decodes into if the previous math operation (or ax,ax)
worked out to be equal(ie zero!) then go to the next instruction. if it was
Not Equal, then go to the label NON_ZERO. it would go to NON_ZERO because AX
has a 5 in it, and a 0 is needed to not jump!
In the next loop we would then see the remander of 5 in AX still, and again
the same thing happens...
6 divided by 10 is a result of 0 (into AX) and a remainder (ie 0.6) into DX.
Again DX is saved on the stack, and CX in incremented. We now have 2 elements
on the stack, and CX counts them. Ax is now equal to 0, so an or ax,ax proves
true and the JNE results in a no jump, and the program reaches the
WRITE_DIGIT_LOOP label for the first time.
At this point we have the hex number stored on the stack in MSB --> LSB format
(just right for writing to screen!) and a count of the number of digits in CX.
now, here's a trick...
There's some special insttructions built into the 80xxx series that make use
of the CX register... One of them is the LOOP command.
LOOP does this: Decrements CX by 1. if CX=0, then it goes to the next
instruction. If CX is not one, however, it jumps to the label (in this case
WRITE_HEX_DIGIT) coded next to it.
so there's a loop of 3 instructions to be done CX times. what happens is DX is
popped off the stack, (that's the MSB, the number 5) and the subroutine
WRITE_HEX_DIGIT is called. in the next loop, the number 4 appears in DX and
the same routine is called... note that the LOOP command actually decrements
CX by one, saving us the job of doing it! Quite neat but a trap for young
programmers... At this point, CX will be equal to 0.
2 elements have been popped off the stack, so the stack is back at the point
where we did the PUSH SI..
You may have guessed, write_hex_digit actually does the displaying, and we'll
look at that in a tic.. but at this point the routine has done it's job and
it's time to return to the calling program. We restore all the registers we
saved with the POP commands.. (note how they go in exact reverse order to the
PUSH's) then go back to our caller with a RET (Short for RETURN [Just like
BASIC!])
That's all there is to the main loop!
Now, on to the displaying a digit bit...
this should be fairly clear to you.. it's name indicates that it is also
capable of displaying HEX letters as well... we don't need to worry about
that however, as all our numbers will be between 0 and 9...
the number comes into the routine in the DX register (I like using DX for
number passing :-) )
See if you can guess how it works... The numbers 0-9 appear in the ASCII table
sequentially starting at number 30. The real value of '0' is 30.. got it??
If you want to work out the hex bit, JAE stands for jump if above or equal,
whilst the ADD dl,'a'-10 must pick the letter, right?
It is important to realize that all this time we've been woring on the 16 bit
DX register, but we've only been concerned with the bottom 4 bits!! a bit of a
waste, possibly, but it works and is just as functional as using the dl
register. I've randomly mixed referance to dl and dx knowing that dl works on
the BOTTOM (Remember l for lower, h for higher!) 8 bits of dx.
The contents of dl will now be 30+the original number, which is ascii for the
digit 0-9. All that remains is to actually throw this digit onto the screen.
That's what PRT1 does..
By now, port1 should be self explanitory. Get a pen. Get paper. Scribble down
how YOU think prt1 works. Take 2 minutes maximum. THEN and only THEN look at
the next 2 lines... you may use the book I recommended to look up the INT 021
function (in fact, I insist you do!)
ANSWER: We use AH to indicate which INT 021 function we want, so we load it
with 02, after saving it's orignial value on the stack so that when we return
the calling program will have all registers the same. The calling program
provides the ASCII digit in DL.. the routine expects itthere, so we simply
call int 021, restor AX and return to the caller. Simple, eh! did you figure
it out?
you've just learnt some valuable instructions and methods used in ASM. the
CALL -- RET sequence is the same as BASIC's gosub -- return sequence, the loop
function repeats CX times, you can temporarily save registers on the stack..
Add anthing else you feel you're getting used to... Quite a bit, isn't it!
Don't say I didn't warn you!
If you wish to test this routine for yourself (and I suggest you play with it
for a while) then code this is A86:
BEGIN: mov DX,<stick in a hex value between 00000h and 0ffffh)
call WRITE_ASCII
int 020
; at this point type in all hte code presented above as it appears. pay no
; regard to case - the assembler is not case sensitive and I do it for clarity
; only!!
WRITE_ASCII:
.
.
.
.
PRT1:
.
.
.
.
RET
Assemble this with a random value. Calculate the decimal version (use a
calculator or something!) and see if it works or not....
Hint: FFFF = 65535 00FF = 256 08C4 = 2244 Honest!!
each time you run it, the number in DX will be printed to the screen.. If you
feel confidant, fiddle round and see the effect of changing various bits..
the worst you can do is lock up the machine!
as a challenge, save the CX register somewhere, and display it after the
number to count the number rof digits in the number!
A hint: Use the prt1 routine tp rint a space character to separate the 2
numbers, and don't use PUSH to save CX... WHY NOT??
Some comments on structured programming
You should all know what that is all about....
If not, it basicly says that you divide your chunks of code up into sections
that only do one, fairly specific job, then call these chunks as you need. Each
chunk (Well, subroutine or procedure are other names for them, but I'll use
chunks! [just to be different]) should have one enty point (the bit you CALL)
and one exit point (IE No JMP's into other chunks, no coding multiple RET's
into the one chunk) and should not upset the operation of any other chunk (the
reason for the saving registers on the stack)
Examine the above code.. you see the first chunk converts raw hex to a
numerical digit. A second chunk is called to do the conversion to ASCII, and a
third chunk to display it on screen. Each chunk is independant (save the
actual parameters passed to it and it's output) and can operate from any
number of calling routines... the WRITE_HEX_DIGIT routine could be called
directly with a hex digit in DL and would print a HEX digit to the screen.
This is the very essence of modular (Structured) programming. If one does not
follow this system (in any language, but most importantly in ASM where you
code tends to become impossible to understand very easily) then things soon
become a real shit mess that even you cannot follow, much less debug!
Shit mess code that has evolved without thought to structure is often referred
to as SPAGHETTI CODING - because it's like trying to sort out a bowl of said
material - near impossible to find the true start and end for all the straggly
bits!
Librarys of usefull routines are soon built up from this (I use the above
routine ALL the time in my text based programs) that you know you can drop in
any program when required and not have to change anything to accomidate it..
I cannot stress enough how much simpler a set of worked out, debugged, easy to
call routines makes your programming life!
------------------------------------------------------------------
That just about draws together the end of lesson 2 unfortunately... It's
grown fair bit larger than I'd first hoped, but who cares, I think if I made
it any smaller, you'd loose track of it...
Next lesson : using A86 and D86 together, more programming examples and a look
at the INT 021 functions...
In the meantime, read you book from cover to cover. digest as much as you can,
and what you can't, don't worry about... you should be beginnig to understand
the register calling convention used with INT 021 - so I suggest you read up
on that a bit! I'll have a lot to say about MS-DOS and it's problems, quirks,
hassles etc.... as well as some undoccumented stuff as time goes on...
Until then, good coding! .\\erlin 8/91